package state

import (
	"errors"
	"alex/msc/helper"
	"alex/ptp1588boundaryclock/datasets"
	"alex/ptp1588boundaryclock/datatypes"
	"alex/ptp1588boundaryclock/com"
	"alex/ptp1588boundaryclock/com/general"
)

// =====================================================================================================================
// Constants
// =====================================================================================================================
// State decision codes
const (
	M1 uint8 = iota
	M2
	M3
	P1
	P2
	S1
)

// PortState 8.2.5.3.1
const (
	_ uint8 = iota
	INITIALIZING
	FAULTY
	DISABLED
	LISTENING
	PRE_MASTER
	MASTER
	PASSIVE
	UNCALIBRATED
	SLAVE
)

// =====================================================================================================================
// Port Slice
// =====================================================================================================================
type PortHandler struct {
	defaultDS *datasets.DefaultDS
	currentDS *datasets.CurrentDS
	parentDS *datasets.ParentDS
	clock *Clock
	ports                []*Port
	superAnnounceMessage general.AnnounceMessage
	// Could be done with Port[i].foreignDS.ForeignMasterPortIdentity.ClockIdentity
	// But multiple look-ups (every time an announce message is added) could slow the machine
	portMapper           []datatypes.ClockIdentity
	globalAnnounceCtr    int
	msgThreshold         int
}

// =====================================================================================================================
// Manage Data
// =====================================================================================================================
// Initializes the PortHandler and creates the *Port slice
func (p* PortHandler) Init(defaultDS *datasets.DefaultDS, currentDS *datasets.CurrentDS, parentDS *datasets.ParentDS,
	clock *Clock, msgThreshold int) {
	p.defaultDS = defaultDS
	p.currentDS = currentDS
	p.parentDS = parentDS
	p.clock = clock
	p.globalAnnounceCtr = 0
	p.msgThreshold = msgThreshold
	p.ports = make([]*Port, 0, 255)
	p.portMapper = make([]datatypes.ClockIdentity, 0, 255)
	// At least one port is running at start-up
	p.createPort(uint16(0))
	/*	p.portMapper = append(p.portMapper, datatypes.ClockIdentity{0,0,0,0,0,0,0,0})
		p.Ports = append(p.Ports, nil)*/
	// fires up the listener
	go p.startListener()
}

// Adds a single *Port to the *Port slice
func (p* PortHandler) addPort(port *Port) (err error) {
	if len(p.ports) == 255 {
		return errors.New("portHandler: Maximum Port Number reached (255)")
	}
	p.ports = append(p.ports, port)
	p.defaultDS.NumberPorts += 1
	return nil
}

// Adds a slice of ports to the *Port slice
func (p* PortHandler) addPorts(ports []*Port) (err error) {
	if len(ports) > 255 {
		return errors.New("portHandler: Slice is to big (255)")
	}
	for _, value := range ports {
		p.addPort(value)
	}
	return nil
}

// Creates a Port
func (p* PortHandler) createPort(portID uint16) (err error) {
	portDS := new(datasets.PortDS)
	identity := new(datatypes.PortIdentity)
	identity.ClockIdentity = p.defaultDS.ClockIdentity
	identity.PortNumber = uint16(portID)
	portDS.Init(*identity, INITIALIZING, datatypes.TimeInterval{0}, uint8(0x01), uint8(2))

	// make timeProperties
	timePropertiesDS := new(datasets.TimePropertiesDS)
	timePropertiesDS.Init(datasets.INTERNAL_OSCILLATOR)
	port := new(Port)
	port.Init(p, portDS, timePropertiesDS, p.clock)
	err = p.addPort(port)
	return err
}

/*func (p* PortHandler) StartListening() {
	for key, _ := range p.Ports {
		p.Ports[key].Listening()
	}
}*/

// =====================================================================================================================
// BMC
// =====================================================================================================================
// Computes the best Announce Message using the bmc.DataSetComparison Algorithm
func (p* PortHandler) computeSuperMessage() {
	tmpPort := new(Port)
	tmpPort.Init(nil, nil, nil, nil)
	for _, value := range p.ports {
		tmpPort.addAnnounceMessage(value.bestAnnounceMsg)
	}
	err := make(chan error)
	go tmpPort.compare(err)
	helper.ErrHandling(<-err)
	if err == nil {
		p.superAnnounceMessage = tmpPort.bestAnnounceMsg
	}
}

// Starts the BMC algorithm
func (p* PortHandler) startBMC() {
	//readyChannel := make(chan error)
	for key, _ := range p.ports {
		// fire up all routines
		//go p.Ports[key].Compare(readyChannel)
		p.ports[key].bMCSignalChannel <- nil
	}
	// ...and wait for them to finish the job
	for key, _ := range p.ports {
		helper.ErrHandling(<-p.ports[key].bMCSignalChannel)
	}

	p.computeSuperMessage()
	p.stateDecisionEvent()
}

// 9.3.3
// Add concurrency if possible -> maybe function over channel
func (p* PortHandler) stateDecisionEvent() {
	slave := false
	for key, _ := range p.ports {
		p.ports[key].dSUpdateSignalChannel <- true
	}
	// wait for all ports to finish
	for key, _ := range p.ports {
		<-p.ports[key].dSUpdateSignalChannel
	}
	// if one port is slave, slave wins!
	for _, value := range p.ports {
		if value.stateDecisionCode == S1 {
			slave = true
			p.currentDS.StepsRemoved = p.superAnnounceMessage.StepsRemoved+1
			p.parentDS.GrandmasterIdentity = p.superAnnounceMessage.ParentDS.GrandmasterIdentity
			p.parentDS.GrandmasterClockQuality = p.superAnnounceMessage.ParentDS.GrandmasterClockQuality
			p.parentDS.GrandmasterPriority1 = p.superAnnounceMessage.ParentDS.GrandmasterPriority1
			p.parentDS.GrandmasterPriority2 = p.superAnnounceMessage.ParentDS.GrandmasterPriority2
			break
		}
	}
	// no port slave => grandmaster clock :-)
	if !slave {
		p.currentDS.StepsRemoved = 0
		p.currentDS.OffsetFromMaster = datatypes.TimeInterval{0}
		p.currentDS.MeanPathDelay = datatypes.TimeInterval{0}
		p.parentDS.ParentPortIdentity.ClockIdentity = p.defaultDS.ClockIdentity
		p.parentDS.ParentPortIdentity.PortNumber = 0
		p.parentDS.GrandmasterIdentity = p.defaultDS.ClockIdentity
		p.parentDS.GrandmasterPriority1 = p.defaultDS.Priority1
		p.parentDS.GrandmasterPriority2 = p.defaultDS.Priority2
	}
}

// =====================================================================================================================
// Message Passing
// =====================================================================================================================
// Passes announce Messages to the corresponding port
// If the port for the corresponding clock is not found it will be created and the message will be passed
func (p* PortHandler) messageHandler(msgs chan general.AnnounceMessage) {
	for {
		announce := <-msgs
		// Don't receive own messages...
		if *announce.GetIdentity() != *p.defaultDS.GetIdentity() {
			portID, found := p.portMapping(announce.SourcePortIdentity.ClockIdentity)

			// could be much simpler by adding a new port only when it is necessary and
			// ignoring the all INIT stuff...
			// OR is needed because one Port will be created at startup
			if found || p.ports[portID] != nil {
				p.ports[portID].announceMessageChannel <- announce
				// standard is ignored here since the new port is set to INITIALIZING even though the others are not!
			} else {
				err := p.createPort(uint16(portID + 1))
				if err == nil {
					p.ports[portID].announceMessageChannel <- announce
				} else {
					helper.ErrHandling(err)
				}
			}
			// Every x message BMC is exectued
			p.globalAnnounceCtr++
			if p.globalAnnounceCtr > p.msgThreshold {
				p.startBMC()
			}
		}
	}
}

// Assigns each unique ClockIdentity a unique port
func (p* PortHandler) portMapping(identity datatypes.ClockIdentity) (port int, found bool) {
	// look for the specific port and return the position
	for key, value := range p.portMapper {
		if value == identity {
			port = key
			found = true
			break
		}
	}
	// if it is not found add a new entry to the slice and inform the caller via (found bool)
	if !found {
		p.portMapper = append(p.portMapper, identity)
		port = len(p.portMapper)-1
	}
	return
}

// Creates a receive channel and passes it to the listener and the portHandler
// Should only be used once
func (p* PortHandler) startListener() {
	announceMsg := make(chan general.AnnounceMessage, 100)
	// go com.MessageListener("320", announceMsg)
	// D.3 Multicast address
	// Why 1320? -> 320 needs admin rights
	go com.MessageListener("1320", true, "224.0.1.129", announceMsg)
	go p.messageHandler(announceMsg)
}

// =====================================================================================================================
// Debugging
// =====================================================================================================================
// PortFactory quickly creates ports - should only be used at start-up!
// In fact it should not be used at all, it is here for debugging reasons
func (p* PortHandler) PortFactory(anz int) {
	for i := 0; i < anz; i++ {
		// make portDS
		portDS := new(datasets.PortDS)
		identitiy := new(datatypes.PortIdentity)
		identitiy.ClockIdentity = p.defaultDS.ClockIdentity
		identitiy.PortNumber = 1
		portDS.Init(*identitiy, INITIALIZING, datatypes.TimeInterval{0}, uint8(0x01), uint8(2))

		// make timeProperties
		timePropertiesDS := new(datasets.TimePropertiesDS)
		timePropertiesDS.Init(datasets.INTERNAL_OSCILLATOR)
		port := new(Port)
		port.Init(p, portDS, timePropertiesDS, p.clock)
		p.addPort(port)
	}
}
